/*
 * Author: Steffen Reith (steffen.reith@hs-rm.de)
 *
 * Creation Date:  Sat Sep 22 21:50:27 GMT+2 2018
 * Module Name:    J1Jtag - A JTAG implementation for the J1 processor
 * Project Name:   J1Sc   - A simple J1 implementation in Scala using Spinal HDL
 *
 * Remark: This interface uses an own clock-domain controlled by tck. Hence the user has to use an special
 * clocking-area environment
 */
import spinal.core._
import spinal.lib._
import spinal.lib.fsm._

import scala.sys._

import java.io._
import java.util.Calendar

class J1Jtag(j1Cfg   : J1Config,
             jtagCfg : JTAGConfig) extends Component {

  // Signals used for the JTAG interface
  val io = new Bundle {

    // JTAG data input
    val tdi = in Bool

    // JTAG data output
    val tdo = out Bool

    // Control for the JTAG TAP
    val tms = in Bool

  }.setName("")

  // A very simple bus used for all internal signals sent or received by the core
  val jtagDataFlow = master Flow(J1JtagData(j1Cfg))

  // Contain all signals used without synchronization by the CPU-core
  val asyncSignals = new Bundle {

    // Asynchron (wrt. the CPU-core) reset signal generated by the jtag interface to reset the CPU-core
    val jtagReset = out Bool

  }.setName("")

  // Indicates that the jtag data is valid
  val jtagDataValid = Bool

  // Patterns to check whether a JTAG command has a read, write or output constant semantic
  val readModePattern     = ".*r.*"
  val writeModePattern    = ".*w.*"
  val constantModePattern = ".*c.*"

  // Command id codes
  val stallId      = 4 * 1 + 1
  val resetId      = 4 * 2 + 1
  val captureMemId = 4 * 3 + 1
  val setAdrId     = 4 * 4 + 1
  val setDataId    = 4 * 5 + 1
  val idCodeId     = 4 * 6 + 1

  // JTAG-command to be implemented (format Name x IDVal x ID x dataRegWidth x Mode x constant value)
  val bypassCmd     = ("BYPASS",     -1,           B(jtagCfg.irWidth bits, default -> True),                   1, "rw", -1)
  val stallCmd      = ("STALL",      stallId,      B(stallId,              jtagCfg.irWidth bits),              1, "rw", -1)
  val resetCmd      = ("RESET",      resetId,      B(resetId,              jtagCfg.irWidth bits),              1, "rw", -1)
  val captureMemCmd = ("CAPTUREMEM", captureMemId, B(captureMemId,         jtagCfg.irWidth bits),              1, "rw", -1)
  val setAdrCmd     = ("SETADR",     setAdrId,     B(setAdrId,             jtagCfg.irWidth bits), j1Cfg.adrWidth, " w", -1)
  val setDataCmd    = ("SETDATA",    setDataId,    B(setDataId,            jtagCfg.irWidth bits), j1Cfg.wordSize, " w", -1)
  val idCodeCmd     = ("IDCODE",     idCodeId,     B(idCodeId,             jtagCfg.irWidth bits),             32, " c", jtagCfg.idCodeValue)

  // List of all implemented JTAG-commands, where BYPASS is mandatory and has to be the first entry
  val jtagCommands = bypassCmd :: idCodeCmd :: stallCmd :: resetCmd :: captureMemCmd :: setAdrCmd :: setDataCmd :: Nil

  // The JTAG instruction register
  val instructionShiftReg = Reg(Bits(jtagCfg.irWidth bits)) randBoot()
  val instructionHoldReg  = Reg(Bits(jtagCfg.irWidth bits)) randBoot()

  // Path to the generated file holding information about the jtag interface
  val jtagInfoPath = "support/openocd/gen/jinfo"

  // Write all information (register width, command codes to an extern file for open openocd)
  try {

    // Open the file
    val writer = new PrintWriter(new File(jtagInfoPath))

    // Write a small header
    writer.write("# Jtag info generated on " + Calendar.getInstance().getTime() + "\n\n")

    // Write info about how long to wait in idle mode after a JTAG-command
    writer.write("set _ocdSleepAmount 2\n\n")
    
    // Write symbolic constants for boolean value
    writer.write("set _ocdTrue 1\n")
    writer.write("set _ocdFalse 0\n\n")

    // Write instruction register width
    writer.write("set _irwidth " + jtagCfg.irWidth + "\n\n")

    // Iterate over all available jtag commands
    for ((name, id, _ , width, _, _) <- jtagCommands) {

      writer.write("set _" + name + "_id " + id + "\n")
      writer.write("set _" + name + "_width " + width + "\n\n")

    }

    // Close the file
    writer.close()

  } catch {

    case i : IOException =>

      // Report an IOException
      println("[J1Sc] IO exception while generating the jtag info (" + jtagInfoPath + ")")

      // Terminate program
      exit(-1)

    case e : Exception =>

      // Unknown exception
      println("[J1Sc] General exception while generating the jtag info (" + jtagInfoPath + ")")

      // Terminate program
      exit(-1)

  }

  // Give some infos about the idcode command
  println("[J1Sc]   Idcode is " + idCodeCmd._3 + " (value: 0x" + idCodeCmd._6.toHexString + ")")

  // For all JTAG instructions create a data hold register
  val dataHoldRegs = Vec(for((name, _, _, width, mode, _) <- jtagCommands) yield {

    // Write a message
    println("[J1Sc]   Create register for JTAG command " +
            name +
            " (Width is " +
            width +
            " bits) with mode >>" +
            mode.replaceAll("""^\s+(?m)""","") +
            "<<")

    // Create the corresponding data register (if needed)
    Reg(Bits(width bits)).allowPruning()

  })

  // Set init value of STALL, RESET and MEMCAPTURE hold register
  dataHoldRegs(jtagCommands.indexOf(stallCmd)).init(0)
  dataHoldRegs(jtagCommands.indexOf(resetCmd)).init(0)
  dataHoldRegs(jtagCommands.indexOf(captureMemCmd)).init(0)

  // For all JTAG instructions create a data shift register
  val dataShiftRegs = Vec(for((_, _,  _, width, _, _) <- jtagCommands) yield {

    // Create the corresponding data register
    Reg(Bits(width bits))

  })

  // Create the JTAG FSM (see https://www.fpga4fun.com/JTAG2.html)
  val jtagFSM = new StateMachine {

    // Reset the FSM and init the registers if needed
    val testLogicReset = new State with EntryPoint

    // Idle the JTAG FSM
    val runTestIdle    = new State

    // States related to the data register
    val selectDRScan   = new State
    val captureDR      = new State
    val shiftDR        = new State
    val exit1DR        = new State
    val pauseDR        = new State
    val exit2DR        = new State
    val updateDR       = new State

    // States related to the instruction register
    val selectIRScan   = new State
    val captureIR      = new State
    val shiftIR        = new State
    val exit1IR        = new State
    val pauseIR        = new State
    val exit2IR        = new State
    val updateIR       = new State

    // By default the data send to the CPU core is invalid
    jtagDataValid := False

    // Avoid a latch and feed the lsb of BYPASS to tdo (in case of an invalid id)
    io.tdo := dataShiftRegs(0).lsb

    // Generate output for all JTAG data shift registers
    for(((_ , _, id , _, _, _), i) <- jtagCommands.zipWithIndex) {

      // check whether the command with id is a active
      when (instructionHoldReg === id) {

        // Output data from the ith shift register
        io.tdo := dataShiftRegs(i).lsb

      }

    }

    // Handle the reset state of the JTAG FSM (data send by the jtag to the core is valid in reset state)
    testLogicReset.whenIsActive {

      // Jtag data send to the CPU core is valid at the moment
      jtagDataValid := True

      // Set to idcode mode and the corresponding data register by default
      instructionHoldReg := idCodeCmd._3
      dataHoldRegs(jtagCommands.indexOf(idCodeCmd)) := (idCodeCmd._3).resized

      // Implement the transition logic
      when(io.tms) { goto(testLogicReset) } otherwise{ goto(runTestIdle) }

    }

    // Implement the idle state (send value of data registers to the core)
    runTestIdle.whenIsActive {

      // Jtag data send to the CPU core is valid when idle
      jtagDataValid := True

      // Check whether test mode is activated and we start to navigate through the state machine
      when(io.tms) { goto(selectDRScan) } otherwise { goto(runTestIdle) }

    }

    // Define the transition function for states related to the data register
    selectDRScan.whenIsActive {

      when(io.tms) { goto(selectIRScan) } otherwise{ goto(captureDR) }

    }

    exit1DR.whenIsActive { when(io.tms) { goto(updateDR) } otherwise{ goto(pauseDR) }}
    pauseDR.whenIsActive { when(io.tms) { goto(exit2DR)  } otherwise{ goto(pauseDR) }}
    exit2DR.whenIsActive { when(io.tms) { goto(updateDR) } otherwise{ goto(shiftDR) }}

    // Handle the capture of all data registers (copy content of data register to the corresponding shift register)
    captureDR.whenIsActive {

      // Generate decoders for all JTAG data registers
      for (((_, _, id, width, mode, loadValue), i) <- jtagCommands.zipWithIndex) {

        // Check whether we need a read mode
        if (mode.matches(readModePattern) && !mode.matches(constantModePattern)) {

          // Generate decoder for the ith data register
          when (instructionHoldReg === id) {

            // Capture the ith data register
            dataShiftRegs(i) := dataHoldRegs(i)

          }

        }

        // Check whether we have to load a constant value
        if (mode.matches(constantModePattern)) {

          // Generate decoder for the ith data register
          when (instructionHoldReg === id) {

            // Init the register with the corresponding constant value
            dataShiftRegs(i) := B(loadValue, width bits)

          }

        }

      }

      // Define the transition function for this state
      when(io.tms) { goto(exit1DR) } otherwise{ goto(shiftDR) }

    }

    // Handle the data shift-register
    shiftDR.whenIsActive {

      // Generate code of all JTAG data shift registers
      for(((_, _, id, _, _, _), i) <- jtagCommands.zipWithIndex) {

        // Generate decoder for the ith data register
        when (instructionHoldReg === id) {

          // Add data to ith shift register
          dataShiftRegs(i) := (io.tdi ## dataShiftRegs(i)) >> 1

        }

      }

      // Define the transition function for this state
      when(io.tms) { goto(exit1DR) } otherwise{ goto(shiftDR) }

    }

    // Move the received data for the shift register to data register
    updateDR.whenIsActive {

      // Generate decoder for all JTAG data registers
      for(((_, _, id, _,mode, _), i) <- jtagCommands.zipWithIndex) {

        // Check whether we need a write mode access
        if(mode.matches(writeModePattern) && !mode.matches(constantModePattern)) {

          // Generate decoder for the ith data register
          when (instructionHoldReg === id) {

            // Update the ith data register
            dataHoldRegs(i) := dataShiftRegs(i)

          }

        }

      }

      // Define the transition function for the this state
      when(io.tms) { goto(selectDRScan) } otherwise{ goto(runTestIdle) }

    }

    // Define the transition function for states related to the instruction register
    selectIRScan.whenIsActive{when(io.tms) { goto(testLogicReset) } otherwise{ goto(captureIR) }}
    exit1IR.whenIsActive     {when(io.tms) { goto(updateIR)       } otherwise{ goto(pauseIR)   }}
    pauseIR.whenIsActive     {when(io.tms) { goto(exit2IR)        } otherwise{ goto(pauseIR)   }}
    exit2IR.whenIsActive     {when(io.tms) { goto(updateIR)       } otherwise{ goto(shiftIR)   }}

    // Handle the capture of the instruction register
    captureIR.whenIsActive {

      // Set instruction register (note tat 01 for the two LSBs is mandatory)
      instructionShiftReg := (0 -> True, 1 -> False, default -> True)

      // Define the transition function for this state
      when(io.tms) { goto(exit1IR) } otherwise{ goto(shiftIR) }

    }

    // Handle the instruction shift-register
    shiftIR.whenIsActive {

      // Feed lsb of instruction register to tdo
      io.tdo := instructionShiftReg.lsb

      // Add data to shift register
      instructionShiftReg := (io.tdi ## instructionShiftReg) >> 1

      // Define the transition function for the this state
      when(io.tms) { goto(exit1IR) } otherwise{ goto(shiftIR) }

    }

    // Move the next instruction (received in the shift register) to the instruction register
    updateIR.whenIsActive {

      // Copy data from shift register to the instruction register
      instructionHoldReg := instructionShiftReg

      // Define the transition function for the this state
      when(io.tms) { goto(selectDRScan) } otherwise{ goto(runTestIdle) }

    }

    // Now the value of the data registers are valid
    updateDR.onExit { jtagDataValid := True }
    updateIR.onExit { jtagDataValid := True }

  }

  // Create the payload bundle
  val jtagDataBundle = new J1JtagData(j1Cfg)

  // Find and provide the data register of STALL (register has only one bit)
  jtagDataBundle.jtagStall := dataHoldRegs(jtagCommands.indexOf(stallCmd))(0)

  // Find and provide the data register of RESET (register has only one bit)
  asyncSignals.jtagReset := dataHoldRegs(jtagCommands.indexOf(resetCmd))(0)

  // Set the valid flag
  jtagDataBundle.jtagDataValid := jtagDataValid

  // Find and provide the data for the memory bus
  jtagDataBundle.jtagCaptureMemory := dataHoldRegs(jtagCommands.indexOf(captureMemCmd))(0)
  jtagDataBundle.jtagCPUAdr        := dataHoldRegs(jtagCommands.indexOf(setAdrCmd))
  jtagDataBundle.jtagCPUWord       := dataHoldRegs(jtagCommands.indexOf(setDataCmd))

  // Send the payload into the flow
  jtagDataFlow.payload := jtagDataBundle

  // Make the payload valid in state where the data is stable
  jtagDataFlow.valid := jtagDataValid

}
